home *** CD-ROM | disk | FTP | other *** search
- ; PLAYWAV.ASM
- ;
- ; Version 1.2 - July 13, 1994. Modified to improve the chip detection
- ; routine.
- ;
- ; This is a program to play .wav files on the Tandy 1000 SL/TL/RL models
- ; (and other Tandy models) with the built-in DAC. The most common types
- ; of .wav are playable: 8- or 16-bit mono or stereo, at sampling rates
- ; up to 65535 Hz.
- ;
- ; Syntax:
- ; PLAYWAV <filename>
- ; There are no command-line options, and the program doesn't display anything
- ; on the screen unless there's an error.
- ;
- ; This program is based on information contained in the _Tandy 1000TL
- ; Technical Reference Manual_; the TLSLSND package by Bruce M. Terry, Jr.;
- ; and the RIFF WAVE file format specification excerpted by Rob Ryan from the
- ; official Microsoft "Multimedia Programming Interface and Data Specification,
- ; v.1.0."
- ;
- ; This program is in .exe format due to memory requirements greater than
- ; 64k.
- ;
- ; Order of segments: code first.
- ;
- SCODE SEGMENT
- SCODE ENDS
- ;
- ; Static data.
- ;
- ; Error and debugging messages.
- ;
- SDATA SEGMENT
- NODACMSG DB "Tandy DAC not detected.",0Dh,0Ah,"$"
- NONAMEERR DB "Must specify input .wav file.",0Dh,0Ah,"$"
- FOPENERR DB "Open failed on input file.",0Dh,0Ah,"$"
- READERRMSG DB "Error reading .wav file.",0Dh,0Ah,"$"
- BADWAVMSG DB ".wav file invalid or unsupported type.",0Dh,0Ah,"$"
- UNSUPPMSG DB "Unsupported sample size or number of channels in"
- DB " .wav file.",0Dh,0Ah,"$"
- UNDERFLOWMSG DB "Output underflow - unable to maintain sampling rate."
- DB 0Dh,0Ah
- DB "Copy .wav to hard disk or RAM disk and try again,"
- DB 0Dh,0Ah
- DB "convert to 8-bit mono, or convert to reduce sampling"
- DB 0Dh,0Ah,"rate.",0Dh,0Ah,"$"
- ;
- ; Default interrupt vectors, for restoring at exit.
- ;
- INT1BDEFAULT DD 0
- ;
- ; Default control-C check flag, for restoring at exit.
- ;
- CTRLC DB 0
- ;
- ; Flag, indicates .wav type. 0 = 8-bit mono PCM (special
- ; case, makes fast code). 1 = 8-bit stereo (requires mixing).
- ; 2 = 16-bit mono (requires conversion to 8-bit). 3 = 16-bit
- ; stereo (requires mixing and converstion to 8-bit). 4 =
- ; unsupported type.
- ;
- WAVTYPE DW 0
- NCHANNELS DW 0 ; number of channels
- SAMPRATE DW 0 ; sampling rate
- DMACLOCK DD 3579545 ; DMA clock rate, for computing divider
- DIVIDER DW 0 ; DAC divider value to match SAMPRATE
- SAMPLESIZE DW 0 ; bytes per multichannel sample
- SAMPLES32K DW 0 ; number of multichannel samples in 32k
- SHIFT DB 0 ; number of shifts to divide by the
- ; number of bytes in a multichannel
- ; sample
- ;
- ; Flag, 1 = format chunk processed.
- ;
- FMTDONE DB 0
- ;
- ; Array of pointers to .wav playing procedures.
- ;
- WAVPROCS DW OFFSET MONO8
- DW OFFSET STEREO8
- DW OFFSET MONO16
- DW OFFSET STEREO16
- ;
- ; File handle for .wav file.
- ;
- HANDLE DW 0
- ;
- ; Small input buffer and comparison strings for analyzing
- ; the header.
- ;
- HEADERBUF DB 12 DUP (0)
- RIFFSTR DB "RIFF" ; RIFF signature
- WAVESTR DB "WAVE" ; WAVE signature
- FMTSTR DB "fmt " ; format chunk header
- DATASTR DB "data" ; data chunk header
- ;
- ; Pointers to current buffers and full/empty flags.
- ;
- CURRENTPLAY DW 0 ; current play buffer
- FILLSTATUS DB 2 DUP (0) ; 0 = empty; 1 = full
- LASTBUFFER DB 2 DUP (0) ; 1 = this is the last buffer to play
- INBUFFER DW 0 ; number of bytes in last buffer filled
- BUFFERSEGS DW 2 DUP (0) ; buffer segments, initialized at
- ; runtime, due to assembler bug
- DONE DB 0 ; 1 = all data has been played
- SOUNDHALTED DB 0 ; 1 = sound chip finalized after playing
- ;
- ; Underflow flag. This flag is set if the output underflows,
- ; i.e., if the processor does not keep up with the output
- ; sampling rate.
- ;
- UNDERFLOW DB 0
- SDATA ENDS
- ;
- ; Then the stack.
- ;
- STACK SEGMENT STACK
- DW 512 DUP (?)
- STACK ENDS
- ;
- ; First DMA buffer.
- ;
- BUFFER0 SEGMENT
- DB 32768 DUP (?)
- BUFFER0 ENDS
- ;
- ; Second DMA buffer.
- ;
- BUFFER1 SEGMENT
- DB 32768 DUP (?)
- BUFFER1 ENDS
- ;
- ; File buffer, 128k. Sufficient to load 32768 16-bit
- ; stereo samples.
- ;
- FILEBUFFER SEGMENT
- DB 32768 DUP (?)
- FILEBUFFER ENDS
- FB2 SEGMENT
- DB 32768 DUP (?)
- FB2 ENDS
- FB3 SEGMENT
- DB 32768 DUP (?)
- FB3 ENDS
- FB4 SEGMENT
- DB 32768 DUP (?)
- FB4 ENDS
-
- SCODE SEGMENT
- ;
- ; Interrupt handlers. Default Int 15h vector must be in code segment.
- ;
- INT15DEFAULT DD 0
- ;
- ; Control-break handler. Does nothing, disabling control-break.
- ;
- INT1BHDLR PROC FAR
- IRET
- INT1BHDLR ENDP
- ;
- ; Control-C handler. Halts sound output, restores interrupt
- ; vectors, and terminates the program.
- ;
- INT23HDLR PROC FAR
- MOV AX,SEG SDATA
- MOV DS,AX
- CALL HALTSOUND
- CALL UNHOOK
- ;
- ; Restore Int 15h vector to default.
- ;
- PUSH DS
- MOV AX,2515h
- MOV DX,WORD PTR CS:INT15DEFAULT
- MOV DS,WORD PTR CS:INT15DEFAULT+2
- INT 21h
- POP DS
- ;
- ; Restore control-C check flag.
- ;
- MOV AX,3301h
- MOV DL,CTRLC
- INT 21h
- ;
- ; Terminate (no error).
- ;
- MOV AX,4C00h
- INT 21h
- INT23HDLR ENDP
- ;
- ; Replacement critical error handler. Fails the system
- ; call (DOS 3.1 and later only).
- ;
- INT24HDLR PROC FAR
- MOV AL,3
- IRET
- INT24HDLR ENDP
- ;
- ; First Int 15h handler, for intercepting the BIOS call out
- ; made when sound output finishes. First, check for the
- ; magic number indicating end of sound output.
- ;
- INT15HDLR1 PROC FAR
- CMP AX,91FBh
- JE >L0
- JMP DWORD PTR CS:INT15DEFAULT
- ;
- ; Finished playing a sound buffer.
- ;
- L0: PUSH AX
- PUSH BX
- PUSH CX
- PUSH DX
- PUSH SI
- PUSH ES
- PUSH DS
- ;
- ; DS addresses our data, SI is the last buffer played.
- ;
- MOV AX,SEG SDATA
- MOV DS,AX
- MOV SI,CURRENTPLAY
- ;
- ; Check if this is the last buffer. If so, set the done
- ; flag and exit.
- ;
- CMP LASTBUFFER[SI],1
- JNE >L1
- MOV DONE,1
- JMP INT15HDLR1_EXIT
- ;
- ; This is not the last buffer. Mark this buffer empty and
- ; switch play buffers.
- ;
- L1: MOV FILLSTATUS[SI],0
- XOR SI,1
- MOV CURRENTPLAY,SI
- ;
- ; If the current play buffer is not full (and ready to play),
- ; the processor is not keeping up with the output sampling
- ; rate. Set the done flag and the underflow flag and exit.
- ;
- CMP FILLSTATUS[SI],0
- JNE >L2
- MOV DONE,1
- MOV UNDERFLOW,1
- JMP INT15HDLR1_EXIT
- ;
- ; There is more to play. Start it playing.
- ;
- L2: SHL SI,1
- MOV AX,BUFFERSEGS[SI]
- MOV ES,AX
- XOR BX,BX
- MOV AX,8307h
- MOV CX,INBUFFER
- MOV DX,DIVIDER
- INT 1Ah
- ;
- ; Exit.
- ;
- INT15HDLR1_EXIT:
- POP DS
- POP ES
- POP SI
- POP DX
- POP CX
- POP BX
- POP AX
- IRET
- INT15HDLR1 ENDP
- ;
- ; Second Int 15h handler. This handler is used (1) before
- ; the first buffer is played, to make sure the sound chip
- ; is ready; and (2) on termination, when preparing the sound
- ; chip for the next application that uses it.
- ;
- INT15HDLR2 PROC FAR
- CMP AX,91FBh
- JE >L0
- JMP DWORD PTR CS:INT15DEFAULT
- L0: PUSH AX
- PUSH DS
- MOV AX,SEG SDATA
- MOV DS,AX
- MOV SOUNDHALTED,1
- POP DS
- POP AX
- IRET
- INT15HDLR2 ENDP
-
- ;
- ; Subroutines.
- ;
- ; Routine to check whether there is BIOS support for Tandy sound functions.
- ; Sets carry if not. (Modified - July 13, 1994.)
- ;
- CHKDAC PROC NEAR
- PUSH AX
- PUSH CX
- ;
- ; Check for PCMCIA Socket Services.
- ;
- MOV AX,8003h
- XOR CX,CX
- INT 1Ah
- CMP CX,5353h
- JE CHKDAC_NODAC
- ;
- ; Check for Tandy DAC.
- ;
- MOV AX,8100h
- INT 1Ah
- CMP AX,8100h
- JE CHKDAC_NODAC
- ;
- ; DAC found.
- ;
- CLC
- JMP CHKDAC_END
- CHKDAC_NODAC:
- STC
- CHKDAC_END:
- POP CX
- POP AX
- RET
- CHKDAC ENDP
- ;
- ; Routine to hook interrupts needed, other than Int 15h.
- ;
- HOOK PROC NEAR
- PUSH AX
- PUSH BX
- PUSH DX
- PUSH ES
- ;
- ; Save default vectors.
- ;
- MOV AX,351Bh
- INT 21h
- MOV WORD PTR INT1BDEFAULT,BX
- MOV WORD PTR INT1BDEFAULT+2,ES
- ;
- ; Set vectors.
- ;
- PUSH DS
- MOV AX,SEG SCODE
- MOV DS,AX
- MOV DX,OFFSET INT1BHDLR
- MOV AX,251Bh
- INT 21h
- MOV DX,OFFSET INT23HDLR
- MOV AX,2523h
- INT 21h
- MOV DX,OFFSET INT24HDLR
- MOV AX,2524h
- INT 21h
- POP DS
- POP ES
- POP DX
- POP BX
- POP AX
- RET
- HOOK ENDP
- ;
- ; Routine to unhook interrupts before termination, other than
- ; Int 15h.
- ;
- UNHOOK PROC NEAR
- PUSH AX
- PUSH DX
- PUSH DS
- MOV DX,WORD PTR INT1BDEFAULT
- MOV DS,WORD PTR INT1BDEFAULT+2
- MOV AX,251Bh
- INT 21h
- POP DS
- POP DX
- POP AX
- RET
- UNHOOK ENDP
- ;
- ; Routine to start sound output.
- ;
- STARTPLAY PROC NEAR
- PUSH AX
- PUSH BX
- PUSH CX
- PUSH DX
- PUSH ES
- ;
- ; Use the second Int 15h handler now.
- ;
- PUSH DS
- MOV DX,OFFSET INT15HDLR2
- MOV AX,SEG SCODE
- MOV DS,AX
- MOV AX,2515h
- INT 21h
- POP DS
- ;
- ; Prepare the sound chip. (Mr. Terry's code.)
- ;
- MOV SOUNDHALTED,0
- L0: MOV AH,84h
- INT 1Ah
- L1: MOV AH,81h
- INT 1Ah
- JC L1
- CMP SOUNDHALTED,0
- JE L0
- ;
- ; Switch to the first Int 15h handler.
- ;
- PUSH DS
- MOV DX,OFFSET INT15HDLR1
- MOV AX,SEG SCODE
- MOV DS,AX
- MOV AX,2515h
- INT 21h
- POP DS
- ;
- ; Start playing the first buffer.
- ;
- MOV AX,SEG BUFFER0
- MOV ES,AX
- XOR BX,BX
- MOV CX,INBUFFER
- MOV DX,DIVIDER
- MOV AX,8307h
- INT 1Ah
- POP ES
- POP DX
- POP CX
- POP BX
- POP AX
- RET
- STARTPLAY ENDP
- ;
- ; Routine to halt sound output. Sets up the sound chip for
- ; the "next" application.
- ;
- HALTSOUND PROC NEAR
- PUSH AX
- PUSH DX
- ;
- ; Use the second Int 15h handler now.
- ;
- PUSH DS
- MOV DX,OFFSET INT15HDLR2
- MOV AX,SEG SCODE
- MOV DS,AX
- MOV AX,2515h
- INT 21h
- POP DS
- ;
- ; Finalize the sound chip. (Mr. Terry's code.)
- ;
- MOV SOUNDHALTED,0
- L0: MOV AH,84h
- INT 1Ah
- L1: MOV AH,81h
- INT 1Ah
- JC L1
- CMP SOUNDHALTED,0
- JE L0
- POP DX
- POP AX
- RET
- HALTSOUND ENDP
- ;
- ; Error handling routine. Display the error message addressed
- ; by DS:DX, halt sound output, unhook interrupts, and exit the
- ; program with an errorlevel of 1. Note that DOS will close
- ; the input file and restore the vectors for Int 23h and Int
- ; 24h automatically.
- ;
- ERROR PROC NEAR
- MOV AH,9
- INT 21h
- CALL HALTSOUND
- CALL UNHOOK
- ;
- ; Restore Int 15h vector to default.
- ;
- PUSH DS
- MOV AX,2515h
- MOV DX,WORD PTR CS:INT15DEFAULT
- MOV DS,WORD PTR CS:INT15DEFAULT+2
- INT 21h
- POP DS
- ;
- ; Restore control-C check flag.
- ;
- MOV AX,3301h
- MOV DL,CTRLC
- INT 21h
- ;
- ; Terminate with error.
- ;
- MOV AX,4C01h
- INT 21h
- ERROR ENDP
- ;
- ; Get input filename from the command line, returning its
- ; address in ES:DX. Assumes that ES addresses the PSP
- ; on entry.
- ;
- GETFILENAME PROC NEAR
- PUSH AX
- PUSH CX
- PUSH DI
- MOV DI,80h
- MOV CL,ES:[DI]
- XOR CH,CH
- JCXZ GETFILENAME_NONAME
- INC DI
- MOV AL,' '
- REPE SCASB
- JE GETFILENAME_NONAME
- DEC DI
- INC CX
- MOV DX,DI
- REPNE SCASB
- JNE >L0
- DEC DI
- L0: MOV BYTE PTR ES:[DI],0
- POP DI
- POP CX
- POP AX
- RET
- GETFILENAME_NONAME:
- MOV DX,OFFSET NONAMEERR
- CALL ERROR
- GETFILENAME ENDP
- ;
- ; Routine to open the input file. ASCIIZ name passed in
- ; ES:DX.
- ;
- FOPEN PROC NEAR
- PUSH AX
- PUSH DS
- MOV AX,ES
- MOV DS,AX
- MOV AX,3D20h ; open read-only, deny writes
- INT 21h
- POP DS
- JNC >L0
- MOV DX,OFFSET FOPENERR
- CALL ERROR
- L0: MOV HANDLE,AX
- POP AX
- RET
- FOPEN ENDP
- ;
- ; Subroutine to read a small number of bytes (specified in
- ; CX) from the file into HEADERBUF. Returns number of bytes
- ; in AX, pointer to HEADERBUF in DX.
- ;
- READSMALL PROC NEAR
- PUSH BX
- MOV AH,3Fh
- MOV BX,HANDLE
- MOV DX,OFFSET HEADERBUF
- INT 21h
- JC READSMALL_READERR
- CMP AX,CX
- JNE READSMALL_INVALID
- POP BX
- RET
- ;
- ; Error reading .wav file header.
- ;
- READSMALL_READERR:
- MOV DX,OFFSET READERRMSG
- CALL ERROR
- ;
- ; Invalid .wav header, or unsupported .wav type.
- ;
- READSMALL_INVALID:
- MOV DX,OFFSET BADWAVMSG
- CALL ERROR
- READSMALL ENDP
- ;
- ; Subroutine for GETWAVHEADER. Reads 4 bytes from the input
- ; file and checks whether the string read is "fmt ", "data",
- ; another ASCII string, or not ASCII. Returns AL = 0 if
- ; "fmt ", AL = 1 if "data", AL = -1 if another ASCII string.
- ; Halts the program if not ASCII. Assumes ES addresses data
- ; segment. Returns pointer to HEADERBUF in DX.
- ;
- GETCHUNK PROC NEAR
- PUSH BX
- PUSH CX
- PUSH SI
- PUSH DI
- ;
- ; Read 4 bytes from the file.
- ;
- MOV CX,4
- CALL READSMALL
- ;
- ; Check if format chunk.
- ;
- MOV SI,DX
- MOV DI,OFFSET FMTSTR
- REPE CMPSB
- JNE GETCHUNK_CHKDATA
- MOV AL,0
- JMP GETCHUNK_EXIT
- ;
- ; Check if data chunk.
- ;
- GETCHUNK_CHKDATA:
- MOV SI,DX
- MOV DI,OFFSET DATASTR
- MOV CX,4
- REPE CMPSB
- JNE GETCHUNK_CHKASCII
- MOV AL,1
- JMP GETCHUNK_EXIT
- ;
- ; Check if a valid chunk header of another type.
- ;
- GETCHUNK_CHKASCII:
- MOV SI,DX
- MOV CX,4
- GETCHUNK_ASCLOOP:
- LODSB
- CMP AL,20h
- JB GETCHUNK_INVALID
- CMP AL,7Eh
- JA GETCHUNK_INVALID
- LOOP GETCHUNK_ASCLOOP
- MOV AL,-1
- ;
- ; Read the chunk length field and exit.
- ;
- GETCHUNK_EXIT: PUSH AX
- MOV CX,4
- CALL READSMALL
- POP AX
- POP DI
- POP SI
- POP CX
- POP BX
- RET
- ;
- ; Invalid .wav header, or unsupported .wav type.
- ;
- GETCHUNK_INVALID:
- MOV DX,OFFSET BADWAVMSG
- CALL ERROR
- GETCHUNK ENDP
- ;
- ; Subroutine for GETWAVHEADER. This routine reads the
- ; format chunk from the file and records the information
- ; there. Assumes DS:DX addresses chunk length.
- ;
- DOFORMAT PROC NEAR
- PUSH AX
- PUSH BX
- PUSH CX
- PUSH DX
- PUSH SI
- ;
- ; Check chunk length (must be 16).
- ;
- MOV SI,DX
- LODSW
- CMP AX,16
- JNE DOFORMAT_INVALID
- MOV CX,AX
- LODSW
- OR AX,AX
- JNZ DOFORMAT_INVALID
- ;
- ; Read in format chunk.
- ;
- CALL READSMALL
- ;
- ; Verify format tag.
- ;
- MOV SI,DX
- LODSW
- CMP AX,1
- JNE DOFORMAT_INVALID
- ;
- ; Verify number of channels and save.
- ;
- LODSW
- CMP AX,1
- JE DOFORMAT_CHOK
- CMP AX,2
- JE DOFORMAT_CHOK
- MOV WAVTYPE,4
- JMP DOFORMAT_EXIT
- DOFORMAT_CHOK: MOV NCHANNELS,AX
- ;
- ; Get sampling rate.
- ;
- LODSW
- MOV SAMPRATE,AX
- LODSW
- OR AX,AX ; no rate above 65535/sec.
- JNZ DOFORMAT_INVALID
- ;
- ; Get bits per sample.
- ;
- ADD SI,6 ; skip bytes/sec, block align
- LODSW
- OR AX,AX ; 0-bit samples invalid
- JZ DOFORMAT_INVALID
- CMP AX,16
- JNA DOFORMAT_BITSOK
- MOV WAVTYPE,4 ; can't play >16 bit samples
- JMP DOFORMAT_EXIT
- ;
- ; Determine .wav type (0, 1, 2 or 3) and store.
- ;
- DOFORMAT_BITSOK:
- DEC AX
- SHR AX,1
- SHR AX,1
- AND AL,2
- MOV BX,NCHANNELS
- DEC BX
- ADD AX,BX
- MOV WAVTYPE,AX
- ;
- ; Compute divider value for DAC to match sampling rate.
- ;
- DOFORMAT_DODIV: MOV BX,SAMPRATE
- CMP BX,875
- JB DOFORMAT_INVALID
- ;
- ; Compute divider. Round to nearest.
- ;
- MOV AX,WORD PTR DMACLOCK
- MOV DX,WORD PTR DMACLOCK+2
- DIV BX
- SHR BX,1
- CMP BX,DX
- ADC AX,0
- MOV DIVIDER,AX
- ;
- ; Exit.
- ;
- DOFORMAT_EXIT: POP SI
- POP DX
- POP CX
- POP BX
- POP AX
- RET
- ;
- ; Invalid .wav header, or unsupported .wav type.
- ;
- DOFORMAT_INVALID:
- MOV DX,OFFSET BADWAVMSG
- CALL ERROR
- DOFORMAT ENDP
- ;
- ; Subroutine for GETWAVHEADER. This routine skips over an
- ; unknown chunk, updating the file pointer. Assumes DS:DX
- ; addresses the chunk length.
- ;
- SKIPCHUNK PROC NEAR
- PUSH AX
- PUSH BX
- PUSH CX
- PUSH DX
- PUSH SI
- MOV SI,DX
- LODSW
- MOV DX,AX
- LODSW
- MOV CX,AX
- MOV AX,4201h
- MOV BX,HANDLE
- INT 21h
- POP SI
- POP DX
- POP CX
- POP BX
- POP AX
- RET
- SKIPCHUNK ENDP
- ;
- ; Routine to read the .wav header from the file and compute
- ; needed parameters, such as the DAC divider value and sample
- ; size, from it. This version skips unknown chunks.
- ;
- GETWAVHEADER PROC NEAR
- PUSH AX
- PUSH CX
- PUSH DX
- PUSH SI
- PUSH DI
- PUSH ES
- ;
- ; ES addresses data segment.
- ;
- MOV AX,DS
- MOV ES,AX
- ;
- ; Read RIFF and WAVE headers from file.
- ;
- MOV CX,12
- CALL READSMALL
- ;
- ; Verify "RIFF".
- ;
- MOV SI,DX
- MOV DI,OFFSET RIFFSTR
- MOV CX,4
- REPE CMPSB
- JNE GETWAVHEADER_INVALID
- ;
- ; Verify "WAVE".
- ;
- ADD SI,4 ; skip length field
- MOV DI,OFFSET WAVESTR
- MOV CX,4
- REPE CMPSB
- JNE GETWAVHEADER_INVALID
- ;
- ; Loop over chunks until data chunk found.
- ;
- GETWAVHEADER_LOOP:
- CALL GETCHUNK
- CMP AL,0 ; format chunk?
- JNE >L0
- CMP FMTDONE,1 ; error if > 1 format chunk
- JE GETWAVHEADER_INVALID
- CALL DOFORMAT
- MOV FMTDONE,1 ; mark format done
- JMP GETWAVHEADER_LOOP
- L0: CMP AL,1 ; data chunk?
- JNE >L1
- CMP FMTDONE,0 ; error if format chunk does
- JE GETWAVHEADER_INVALID ; not precede data chunk
- JMP GETWAVHEADER_LOOPEND
- L1: CALL SKIPCHUNK ; unknown chunk, skip
- JMP GETWAVHEADER_LOOP
- ;
- ; Data chunk found; skip length field.
- ;
- GETWAVHEADER_LOOPEND:
- MOV CX,4
- CALL READSMALL
- POP ES
- POP DI
- POP SI
- POP DX
- POP CX
- POP AX
- RET
- ;
- ; Invalid .wav header, or unsupported .wav type.
- ;
- GETWAVHEADER_INVALID:
- MOV DX,OFFSET BADWAVMSG
- CALL ERROR
- GETWAVHEADER ENDP
- ;
- ; Routine to play an 8-bit mono .wav. These .wav's require
- ; no mixing or conversion, so the playing routine can be
- ; optimized.
- ;
- MONO8 PROC NEAR
- PUSH AX
- PUSH BX
- PUSH CX
- PUSH DX
- PUSH SI
- ;
- ; Fill first DMA buffer.
- ;
- PUSH DS
- MOV BX,HANDLE
- MOV AX,SEG BUFFER0
- MOV DS,AX
- MOV AH,3Fh
- MOV CX,32768
- XOR DX,DX
- INT 21h
- POP DS
- JNC MONO8_CHKEMPTY
- JMP MONO8_FILEERR
- ;
- ; If zero bytes were read, the file contains no samples (an
- ; unusual case, to say the least). Exit immediately.
- ;
- MONO8_CHKEMPTY: OR AX,AX
- JNZ MONO8_CHKSHORT
- JMP MONO8_EXIT
- ;
- ; If fewer than 32768 (but more than zero) bytes were read,
- ; mark this buffer last, record the number of bytes in it,
- ; mark it full, start playing it, and go wait for it to be
- ; played. This is for a short file with fewer than 32768
- ; samples.
- ;
- MONO8_CHKSHORT: CMP AX,32768
- JE MONO8_NOTSHORT
- MOV LASTBUFFER,1
- MOV INBUFFER,AX
- MOV FILLSTATUS,1
- CALL STARTPLAY
- JMP MONO8_WAITEND
- ;
- ; Not a short file. Record the number of bytes in this
- ; buffer, mark it full, and start playing it.
- ;
- MONO8_NOTSHORT: MOV INBUFFER,AX
- MOV FILLSTATUS,1
- CALL STARTPLAY
- ;
- ; Loop, filling buffers when they become empty. SI is the
- ; current fill buffer.
- ;
- MOV SI,1
- ;
- ; First, wait until the current fill buffer becomes empty.
- ; Halt program with error if underflow is detected.
- ;
- MONO8_LOOP: CMP UNDERFLOW,1
- JE MONO8_UNDERFLOW
- CMP FILLSTATUS[SI],0
- JNE MONO8_LOOP
- ;
- ; Fill buffer.
- ;
- PUSH DS
- MOV BX,HANDLE
- SHL SI,1
- MOV AX,BUFFERSEGS[SI]
- SHR SI,1
- MOV DS,AX
- MOV AH,3Fh
- MOV CX,32768
- XOR DX,DX
- INT 21h
- POP DS
- JC MONO8_FILEERR
- ;
- ; If zero bytes were read, the previous buffer was the last
- ; buffer. Mark it so and exit the loop.
- ;
- OR AX,AX
- JNZ MONO8_NONZERO
- XOR SI,1
- MOV LASTBUFFER[SI],1
- JMP MONO8_WAITEND
- ;
- ; If fewer than 32768 (but more than zero) bytes were read,
- ; mark this buffer last, record the number of bytes in it,
- ; mark it full, and exit the loop.
- ;
- MONO8_NONZERO: CMP AX,32768
- JE MONO8_NOTEND
- MOV LASTBUFFER[SI],1
- MOV INBUFFER,AX
- MOV FILLSTATUS[SI],1
- JMP MONO8_WAITEND
- ;
- ; This is not the last buffer. Record the number of bytes
- ; in it, mark it full, switch buffers, and go again.
- ;
- MONO8_NOTEND: MOV INBUFFER,AX
- MOV FILLSTATUS[SI],1
- XOR SI,1
- JMP MONO8_LOOP
- ;
- ; All data has been read. Wait until it's through playing,
- ; then set up the sound chip for the next application that
- ; uses it.
- ;
- MONO8_WAITEND: CMP DONE,1
- JNE MONO8_WAITEND
- CALL HALTSOUND
- ;
- ; Exit.
- ;
- MONO8_EXIT: POP SI
- POP DX
- POP CX
- POP BX
- POP AX
- RET
- ;
- ; Error reading input .wav file.
- ;
- MONO8_FILEERR: MOV DX,OFFSET READERRMSG
- CALL ERROR
- ;
- ; Output underflow.
- ;
- MONO8_UNDERFLOW:
- MOV DX,OFFSET UNDERFLOWMSG
- CALL ERROR
- MONO8 ENDP
- ;
- ; Subroutine for MONO16 and STEREO16 (which call it READ16)
- ; and STEREO8 (which calls it READ8). This routine reads
- ; 32768 8-bit stereo or 16-bit samples into the input buffer,
- ; if possible. Exits program on file error. Returns AX =
- ; number of samples read.
- ;
- READ16 PROC NEAR
- READ8 EQU READ16
- PUSH BX
- PUSH CX
- PUSH DX
- PUSH SI
- PUSH DI
- MOV CX,SAMPLESIZE ; CX is read loop counter
- XOR SI,SI ; SI counts samples read
- MOV BX,HANDLE ; BX is file handle
- MOV DI,SEG FILEBUFFER ; DI is current segment
- XOR DX,DX ; DX is offset (always zero)
- READ16_LOOP: PUSH CX ; save loop counter
- PUSH DS ; save DS
- MOV AH,3Fh ; read into buffer
- MOV DS,DI
- MOV CX,32768
- INT 21h
- POP DS ; restore DS
- JC READ16_ERROR ; exit if file error
- CMP AX,CX ; if < 32768 bytes read ...
- JB READ16_EOF ; ... compute samples read
- ADD SI,SAMPLES32K ; else add constant
- ADD DI,800h ; go to next 32k segment
- POP CX ; restore loop counter
- LOOP READ16_LOOP
- ;
- ; End of loop. SI contains samples read. Return in AX.
- ;
- READ16_ENDLOOP: MOV AX,SI
- POP DI
- POP SI
- POP DX
- POP CX
- POP BX
- RET
- ;
- ; End-of-file encountered. SI contains number of samples
- ; read, excluding this last read. AX contains number of bytes
- ; read this time, less than 32k.
- ;
- READ16_EOF: POP CX ; clear stack
- MOV CL,SHIFT
- SHR AX,CL
- ADD SI,AX ; add last few samples to count
- JMP READ16_ENDLOOP ; "rejoin the class"
- ;
- ; File error encountered.
- ;
- READ16_ERROR: MOV DX,OFFSET READERRMSG
- CALL ERROR
- READ16 ENDP
- ;
- ; Routine to play an 8-bit stereo .wav. 8-bit .wav's use
- ; unsigned samples.
- ;
- STEREO8 PROC NEAR
- PUSH AX
- PUSH BX
- PUSH CX
- PUSH SI
- PUSH DI
- PUSH ES
- ;
- ; Set sample size, samples per 32k, and number of shifts to
- ; divide by the number of bytes in a sample.
- ;
- MOV SAMPLESIZE,2
- MOV SAMPLES32K,16384
- MOV SHIFT,1
- ;
- ; Fill input buffer.
- ;
- CALL READ8
- ;
- ; If zero bytes were read, the file contains no samples (an
- ; unusual case, to say the least). Exit immediately.
- ;
- OR AX,AX
- JNZ STEREO8_NOTEMPTY
- JMP STEREO8_EXIT
- ;
- ; Save the number of samples.
- ;
- STEREO8_NOTEMPTY:
- MOV INBUFFER,AX
- ;
- ; Fill the first DMA buffer.
- ;
- PUSH AX
- PUSH DS
- MOV CX,AX ; CX counts samples
- MOV ES,BUFFERSEGS ; ES:DI addresses DMA buffer
- XOR DI,DI
- MOV AX,SEG FILEBUFFER ; DS:SI addresses sample data
- MOV DS,AX
- XOR SI,SI
- ;
- ; Loop over stereo 8-bit samples.
- ;
- STEREO8_LP1: LODSW ; get 2 channels
- MOV BL,AH
- XOR AH,AH
- ADD AL,BL
- ADC AH,0
- ;
- ; Divide by number of channels (2) and store in DMA buffer.
- ;
- SHR AX,1
- STOSB
- LOOP STEREO8_LP1 ; go again if more
- POP DS
- POP AX
- ;
- ; If fewer than 32768 (but more than zero) bytes were read,
- ; mark this buffer last, mark it full, start playing sound,
- ; and go wait for it to finish. This is for a short file
- ; with fewer than 32768 samples.
- ;
- CMP AX,32768
- JE STEREO8_NOTSHORT
- MOV LASTBUFFER,1
- MOV FILLSTATUS,1
- CALL STARTPLAY
- JMP STEREO8_WAITEND
- ;
- ; Start playing.
- ;
- STEREO8_NOTSHORT:
- MOV FILLSTATUS,1
- CALL STARTPLAY
- ;
- ; Loop, filling buffers when they become empty. SI is the
- ; current fill buffer.
- ;
- MOV SI,1
- ;
- ; First, wait until the current fill buffer becomes empty.
- ; Halt program with error if underflow is detected.
- ;
- STEREO8_LOOP: CMP UNDERFLOW,1
- JNE STEREO8_CHKFILLSTAT
- MOV DX,OFFSET UNDERFLOWMSG
- CALL ERROR
- STEREO8_CHKFILLSTAT:
- CMP FILLSTATUS[SI],0
- JNE STEREO8_LOOP
- ;
- ; Read in 32k more samples (if possible).
- ;
- CALL READ8
- ;
- ; If zero bytes were read, the previous buffer was the last
- ; buffer. Mark it so and exit the loop.
- ;
- OR AX,AX
- JNZ STEREO8_NONZERO
- XOR SI,1
- MOV LASTBUFFER[SI],1
- JMP STEREO8_WAITEND
- ;
- ; Save number of samples.
- ;
- STEREO8_NONZERO:
- MOV INBUFFER,AX
- ;
- ; Fill the next DMA buffer.
- ;
- PUSH AX
- PUSH SI
- PUSH DS
- MOV CX,AX ; CX counts samples
- SHL SI,1 ; ES:DI addresses DMA buffer
- MOV ES,BUFFERSEGS[SI]
- XOR DI,DI
- MOV AX,SEG FILEBUFFER ; DS:SI addresses sample data
- MOV DS,AX
- XOR SI,SI
- ;
- ; Loop over stereo 8-bit samples.
- ;
- STEREO8_LP2: LODSW ; get 2 channels
- MOV BL,AH ; add the two
- XOR AH,AH
- ADD AL,BL
- ADC AH,0
- ;
- ; Divide by number of channels (2) and store in DMA buffer.
- ;
- SHR AX,1
- STOSB
- LOOP STEREO8_LP2 ; go again if more
- POP DS
- POP SI
- POP AX
- ;
- ; If fewer than 32768 (but more than zero) bytes were read,
- ; mark this buffer last, mark it full, and exit the loop.
- ;
- CMP AX,32768
- JE STEREO8_NOTEND
- MOV LASTBUFFER[SI],1
- MOV FILLSTATUS[SI],1
- JMP STEREO8_WAITEND
- ;
- ; This is not the last buffer. Mark it full, switch buffers,
- ; and go again.
- ;
- STEREO8_NOTEND: MOV FILLSTATUS[SI],1
- XOR SI,1
- JMP STEREO8_LOOP
- ;
- ; All data has been read. Wait until it's through playing,
- ; then set up the sound chip for the next application that
- ; uses it.
- ;
- STEREO8_WAITEND:
- CMP DONE,1
- JNE STEREO8_WAITEND
- CALL HALTSOUND
- ;
- ; Exit.
- ;
- STEREO8_EXIT: POP ES
- POP DI
- POP SI
- POP CX
- POP BX
- POP AX
- RET
- STEREO8 ENDP
- ;
- ; Routine to play 16-bit mono .wav's. These use signed
- ; samples.
- ;
- MONO16 PROC NEAR
- PUSH AX
- PUSH CX
- PUSH SI
- PUSH DI
- PUSH ES
- ;
- ; Set sample size, samples per 32k, and number of shifts to
- ; divide by the number of bytes in a sample.
- ;
- MOV SAMPLESIZE,2
- MOV SAMPLES32K,16384
- MOV SHIFT,1
- ;
- ; Fill input buffer.
- ;
- CALL READ16
- ;
- ; If zero bytes were read, the file contains no samples (an
- ; unusual case, to say the least). Exit immediately.
- ;
- OR AX,AX
- JNZ MONO16_NOTEMPTY
- JMP MONO16_EXIT
- ;
- ; Save the number of samples.
- ;
- MONO16_NOTEMPTY:
- MOV INBUFFER,AX
- ;
- ; Fill the first DMA buffer.
- ;
- PUSH AX
- PUSH DS
- MOV CX,AX ; CX counts samples
- MOV ES,BUFFERSEGS ; ES:DI addresses DMA buffer
- XOR DI,DI
- MOV AX,SEG FILEBUFFER ; DS:SI addresses sample data
- MOV DS,AX
- XOR SI,SI
- ;
- ; Loop over mono 16-bit samples.
- ;
- MONO16_LP1: LODSW ; get a sample
- MOV AL,AH ; convert to 8-bit unsigned
- ADD AL,128
- STOSB ; store in DMA buffer
- LOOP MONO16_LP1 ; go again if more
- POP DS
- POP AX
- ;
- ; If fewer than 32768 (but more than zero) bytes were read,
- ; mark this buffer last. This is for a short file with fewer
- ; than 32768 samples.
- ;
- CMP AX,32768
- JE MONO16_NOTSHORT
- MOV LASTBUFFER,1
- MOV FILLSTATUS,1
- CALL STARTPLAY
- JMP MONO16_WAITEND
- ;
- ; Start playing.
- ;
- MONO16_NOTSHORT:
- MOV FILLSTATUS,1
- CALL STARTPLAY
- ;
- ; Loop, filling buffers when they become empty. SI is the
- ; current fill buffer.
- ;
- MOV SI,1
- ;
- ; First, wait until the current fill buffer becomes empty.
- ; Halt program with error if underflow is detected.
- ;
- MONO16_LOOP: CMP UNDERFLOW,1
- JE MONO16_UNDERFLOW
- CMP FILLSTATUS[SI],0
- JNE MONO16_LOOP
- ;
- ; Read in 32k more samples (if possible).
- ;
- CALL READ16
- ;
- ; If zero bytes were read, the previous buffer was the last
- ; buffer. Mark it so and exit the loop.
- ;
- OR AX,AX
- JNZ MONO16_NONZERO
- XOR SI,1
- MOV LASTBUFFER[SI],1
- JMP MONO16_WAITEND
- ;
- ; Save number of samples.
- ;
- MONO16_NONZERO: MOV INBUFFER,AX
- ;
- ; Fill the next DMA buffer.
- ;
- PUSH AX
- PUSH SI
- PUSH DS
- MOV CX,AX ; CX counts samples
- SHL SI,1 ; ES:DI addresses DMA buffer
- MOV ES,BUFFERSEGS[SI]
- XOR DI,DI
- MOV AX,SEG FILEBUFFER ; DS:SI addresses sample data
- MOV DS,AX
- XOR SI,SI
- ;
- ; Loop over mono 16-bit samples.
- ;
- MONO16_LP2: LODSW ; get a sample
- MOV AL,AH
- ADD AL,128 ; convert to unsigned
- STOSB ; place in DMA buffer
- LOOP MONO16_LP2 ; go again if more
- POP DS
- POP SI
- POP AX
- ;
- ; If fewer than 32768 (but more than zero) bytes were read,
- ; mark this buffer last, mark it full, and exit the loop.
- ;
- CMP AX,32768
- JE MONO16_NOTEND
- MOV LASTBUFFER[SI],1
- MOV FILLSTATUS[SI],1
- JMP MONO16_WAITEND
- ;
- ; This is not the last buffer. Mark it full, switch buffers,
- ; and go again.
- ;
- MONO16_NOTEND: MOV FILLSTATUS[SI],1
- XOR SI,1
- JMP MONO16_LOOP
- ;
- ; All data has been read. Wait until it's through playing,
- ; then set up the sound chip for the next application that
- ; uses it.
- ;
- MONO16_WAITEND: CMP DONE,1
- JNE MONO16_WAITEND
- CALL HALTSOUND
- ;
- ; Exit.
- ;
- MONO16_EXIT: POP ES
- POP DI
- POP SI
- POP CX
- POP AX
- RET
- ;
- ; Output underflow.
- ;
- MONO16_UNDERFLOW:
- MOV DX,OFFSET UNDERFLOWMSG
- CALL ERROR
- MONO16 ENDP
- ;
- ; Routine to play 16-bit stereo .wav's. These use signed
- ; samples and require mixing.
- ;
- STEREO16 PROC NEAR
- PUSH AX
- PUSH BX
- PUSH CX
- PUSH SI
- PUSH DI
- PUSH BP
- PUSH ES
- ;
- ; Set sample size, samples per 32k, and number of shifts to
- ; divide by the number of bytes in a sample.
- ;
- MOV SAMPLESIZE,4
- MOV SAMPLES32K,8192
- MOV SHIFT,2
- ;
- ; Fill input buffer.
- ;
- CALL READ16
- ;
- ; If zero bytes were read, the file contains no samples (an
- ; unusual case, to say the least). Exit immediately.
- ;
- OR AX,AX
- JNZ STEREO16_NOTEMPTY
- JMP STEREO16_EXIT
- ;
- ; Save the number of samples.
- ;
- STEREO16_NOTEMPTY:
- MOV INBUFFER,AX
- ;
- ; Fill the first DMA buffer.
- ;
- PUSH AX
- PUSH DS
- MOV CX,AX ; CX counts samples
- MOV ES,BUFFERSEGS ; ES:DI addresses DMA buffer
- XOR DI,DI
- MOV AX,SEG FILEBUFFER ; DS:SI addresses sample data
- MOV DS,AX
- XOR SI,SI
- ;
- ; Set CX to a maximum of 16k, BP to the remainder of what
- ; CX was.
- ;
- XOR BP,BP
- CMP CX,16384
- JBE STEREO16_LP1
- MOV BP,CX
- MOV CX,16384
- SUB BP,CX
- ;
- ; Loop over stereo 16-bit samples.
- ;
- STEREO16_LP1: LODSW ; get first channel
- MOV AL,AH
- CBW
- MOV BX,AX ; save in BX
- LODSW ; get second channel
- MOV AL,AH
- CBW
- ADD AX,BX ; add into AX
- ;
- ; Divide by number of channels (2), convert to unsigned, and
- ; store in DMA buffer.
- ;
- SAR AX,1
- ADD AX,128
- STOSB
- LOOP STEREO16_LP1 ; go again if more
- MOV CX,BP
- JCXZ STEREO16_LP1END
- XOR BP,BP
- MOV AX,DS
- ADD AH,10h
- MOV DS,AX
- JMP STEREO16_LP1
- STEREO16_LP1END:
- POP DS
- POP AX
- ;
- ; If fewer than 32768 (but more than zero) bytes were read,
- ; mark this buffer last. This is for a short file with fewer
- ; than 32768 samples.
- ;
- CMP AX,32768
- JE STEREO16_NOTSHORT
- MOV LASTBUFFER,1
- MOV FILLSTATUS,1
- CALL STARTPLAY
- JMP STEREO16_WAITEND
- ;
- ; Start playing.
- ;
- STEREO16_NOTSHORT:
- MOV FILLSTATUS,1
- CALL STARTPLAY
- ;
- ; Loop, filling buffers when they become empty. SI is the
- ; current fill buffer.
- ;
- MOV SI,1
- ;
- ; First, wait until the current fill buffer becomes empty.
- ; Halt program with error if underflow is detected.
- ;
- STEREO16_LOOP: CMP UNDERFLOW,1
- JNE STEREO16_CHKFILLSTAT
- MOV DX,OFFSET UNDERFLOWMSG
- CALL ERROR
- STEREO16_CHKFILLSTAT:
- CMP FILLSTATUS[SI],0
- JNE STEREO16_LOOP
- ;
- ; Read in 32k more samples (if possible).
- ;
- CALL READ16
- ;
- ; If zero bytes were read, the previous buffer was the last
- ; buffer. Mark it so and exit the loop.
- ;
- OR AX,AX
- JNZ STEREO16_NONZERO
- XOR SI,1
- MOV LASTBUFFER[SI],1
- JMP STEREO16_WAITEND
- ;
- ; Save number of samples.
- ;
- STEREO16_NONZERO:
- MOV INBUFFER,AX
- ;
- ; Fill the next DMA buffer.
- ;
- PUSH AX
- PUSH SI
- PUSH DS
- MOV CX,AX ; CX counts samples
- SHL SI,1 ; ES:DI addresses DMA buffer
- MOV ES,BUFFERSEGS[SI]
- XOR DI,DI
- MOV AX,SEG FILEBUFFER ; DS:SI addresses sample data
- MOV DS,AX
- XOR SI,SI
- ;
- ; Set CX to a maximum of 16k, BP to the remainder of what
- ; CX was.
- ;
- XOR BP,BP
- CMP CX,16384
- JBE STEREO16_LP2
- MOV BP,CX
- MOV CX,16384
- SUB BP,CX
- ;
- ; Loop over stereo 16-bit samples.
- ;
- STEREO16_LP2: LODSW ; get first channel
- MOV AL,AH
- CBW
- MOV BX,AX ; save in BX
- LODSW ; get second channel
- MOV AL,AH
- CBW
- ADD AX,BX ; add into AX
- ;
- ; Divide by number of channels (2), convert to unsigned, and
- ; store in DMA buffer.
- ;
- SAR AX,1
- ADD AX,128
- STOSB
- LOOP STEREO16_LP2 ; go again if more
- MOV CX,BP
- JCXZ STEREO16_LP2END
- XOR BP,BP
- MOV AX,DS
- ADD AH,10h
- MOV DS,AX
- JMP STEREO16_LP2
- STEREO16_LP2END:
- POP DS
- POP SI
- POP AX
- ;
- ; If fewer than 32768 (but more than zero) bytes were read,
- ; mark this buffer last, mark it full, and exit the loop.
- ;
- CMP AX,32768
- JE STEREO16_NOTEND
- MOV LASTBUFFER[SI],1
- MOV FILLSTATUS[SI],1
- JMP STEREO16_WAITEND
- ;
- ; This is not the last buffer. Mark it full, switch buffers,
- ; and go again.
- ;
- STEREO16_NOTEND:
- MOV FILLSTATUS[SI],1
- XOR SI,1
- JMP STEREO16_LOOP
- ;
- ; All data has been read. Wait until it's through playing,
- ; then set up the sound chip for the next application that
- ; uses it.
- ;
- STEREO16_WAITEND:
- CMP DONE,1
- JNE STEREO16_WAITEND
- CALL HALTSOUND
- ;
- ; Exit.
- ;
- STEREO16_EXIT: POP ES
- POP BP
- POP DI
- POP SI
- POP CX
- POP BX
- POP AX
- RET
- STEREO16 ENDP
-
- ;
- ; Main program.
- ;
- MAIN PROC FAR
- ;
- ; DS addresses data segment (always; if any routine changes
- ; DS, it must restore it before returning).
- ;
- MOV AX,SEG SDATA
- MOV DS,AX
- ;
- ; Initialize BUFFERSEGS.
- ;
- MOV BUFFERSEGS,SEG BUFFER0
- MOV BUFFERSEGS+2,SEG BUFFER1
- ;
- ; Direction set to increment (always; if any routine changes
- ; DF, it must restore it before returning).
- ;
- CLD
- ;
- ; Check whether the needed sound circuitry is present.
- ;
- CALL CHKDAC
- JNC HOOKVECS
- MOV DX,OFFSET NODACMSG
- MOV AH,9
- INT 21h
- JMP TERMINATE
- ;
- ; Hook needed interrupt vectors, other than Int 15h.
- ;
- HOOKVECS: CALL HOOK
- ;
- ; Save default Int 15h vector in code segment.
- ;
- PUSH ES ; save PSP for GETFILENAME
- MOV AX,3515h
- INT 21h
- MOV WORD PTR CS:INT15DEFAULT,BX
- MOV WORD PTR CS:INT15DEFAULT+2,ES
- POP ES ; restore PSP
- ;
- ; Save the control-C check flag status, then turn it on so
- ; that the user can interrupt playing with <cntrl>-C.
- ;
- MOV AX,3300h
- INT 21h
- MOV CTRLC,DL
- MOV AX,3301h
- MOV DL,1
- INT 21h
- ;
- ; Open the input file.
- ;
- CALL GETFILENAME
- CALL FOPEN
- ;
- ; Get and process the .wav header.
- ;
- CALL GETWAVHEADER
- ;
- ; Play the .wav.
- ;
- MOV BX,WAVTYPE
- CMP BX,4
- JAE BADTYPE
- SHL BX,1
- CALL WAVPROCS[BX]
- JMP PLAYDONE
- BADTYPE: MOV DX,OFFSET UNSUPPMSG
- MOV AH,9 ; .wav has unsupported sample size or
- INT 21h ; number of channels
- ;
- ; Unhook interrupts, other than Int 15h.
- ;
- PLAYDONE: CALL UNHOOK
- ;
- ; Restore Int 15h vector to default.
- ;
- PUSH DS
- MOV AX,2515h
- MOV DX,WORD PTR CS:INT15DEFAULT
- MOV DS,WORD PTR CS:INT15DEFAULT+2
- INT 21h
- POP DS
- ;
- ; Restore control-C check flag.
- ;
- MOV AX,3301h
- MOV DL,CTRLC
- INT 21h
- ;
- ; Terminate.
- ;
- TERMINATE: MOV AX,4C00h
- INT 21h
- MAIN ENDP
- SCODE ENDS
- END MAIN